package lol.clann.minecraft.springboot.api.bukkit.model;

import lol.clann.minecraft.springboot.api.bukkit.constant.BlockDirection;
import org.bukkit.World;
import org.bukkit.block.Block;

import java.util.*;
import java.util.function.Consumer;
import java.util.function.Predicate;

/**
 * 提供方块遍历
 * 变换检测等功能
 *
 * @author: zyp
 * @date: 2020/10/27 3:52 下午
 */
public class BlockWalker {
    private LinkedList<ImmutableLocation> pending = new LinkedList<>(); // 将要遍历的方块队列
    private Set<ImmutableLocation> pendingSet = new HashSet<>(); // 对pending去重
    private World world;
    private int x;
    private int y;
    private int z;
    private Predicate<Block> test;

    private BlockWalker(World world, int x, int y, int z, Predicate<Block> test) {
        this.world = world;
        this.x = x;
        this.y = y;
        this.z = z;
        this.test = test;
    }

    public static BlockWalker create(World world, int x, int y, int z, Predicate<Block> test) {
        return new BlockWalker(world, x, y, z, test);
    }

    /**
     * 判断是否形成闭环
     *
     * @return
     */
    public boolean hasCircular() {
        LinkedLocation initLoc = new LinkedLocation(x, y, z);
        Block block = this.world.getBlockAt(initLoc.x, initLoc.y, initLoc.z);
        if (!this.test.test(block)) {
            return false;
        }
        Map<ImmutableLocation, ImmutableLocation> walked = new HashMap(); // 缓存已遍历的方块
        Set<ImmutableLocation> invalid = new HashSet<>(); // 缓存遍历到的无效方块
        initLoc.setBlock(block);
        pending(initLoc);
        walked.put(initLoc, initLoc);
        while (!pending.isEmpty()) {
            LinkedLocation next = (LinkedLocation) next(); // next一定是有效的
            int smaller = 0;
            for (BlockDirection value : BlockDirection.values()) {
                ImmutableLocation relativeLoc = next.getRelative(value);
                ImmutableLocation exist = walked.get(relativeLoc);
                if (exist != null) { // 遍历过了
                    if (((LinkedLocation) exist).level < next.level) {
                        smaller++; // 附近的方块比自己小
                    }
                    if (smaller > 1) {
                        return true; // 相邻方块有多余1个比自己小的,说明形成了闭环
                    }
                    continue;
                }
                if (invalid.contains(relativeLoc)) { // 无效方块
                    continue;
                }
                Block relativeBlock = next.getBlock().getRelative(value.blockFace);
                if (!this.test.test(relativeBlock)) {
                    invalid.add(relativeLoc);
                } else {
                    relativeLoc.setBlock(relativeBlock);
                    walked.put(relativeLoc, relativeLoc);
                    pending(relativeLoc);
                }
            }
        }
        return false;
    }

    /**
     * 从起点遍历所有相邻的满足条件的方块
     *
     * @return
     */
    public List<Block> walk() {
        List<Block> list = new ArrayList<>();
        walk(list::add);
        return list;
    }

    /**
     * 从起点遍历所有相邻的满足条件的方块
     *
     * @param consumer
     */
    public void walk(Consumer<Block> consumer) {
        ImmutableLocation initLoc = new ImmutableLocation(x, y, z);
        Block block = this.world.getBlockAt(initLoc.x, initLoc.y, initLoc.z);
        if (!this.test.test(block)) {
            return;
        }
        initLoc.setBlock(block);
        pending(initLoc);
        Set<ImmutableLocation> walked = new HashSet(); // 缓存已遍历的方块
        Set<ImmutableLocation> invalid = new HashSet(); // 缓存遍历到的无效方块
        walked.add(initLoc);
        consumer.accept(block);
        while (!pending.isEmpty()) {
            ImmutableLocation next = next(); // next一定是有效的
            for (BlockDirection value : BlockDirection.values()) {
                ImmutableLocation relativeLoc = next.getRelative(value);
                if (walked.contains(relativeLoc)) { // 遍历过了
                    continue;
                }
                if (invalid.contains(relativeLoc)) { // 无效方块
                    continue;
                }
                Block relativeBlock = next.getBlock().getRelative(value.blockFace);
                if (!this.test.test(relativeBlock)) {
                    invalid.add(relativeLoc);
                } else {
                    relativeLoc.setBlock(relativeBlock);
                    walked.add(relativeLoc);
                    consumer.accept(relativeBlock);
                    pending(relativeLoc);
                }
            }
        }
    }

    /**
     * 重置
     */
    public void reset() {
        pending.clear();
        pendingSet.clear();
    }

    private ImmutableLocation next() {
        ImmutableLocation next = pending.pop();
        pendingSet.remove(next);
        return next;
    }

    private void pending(ImmutableLocation loc) {
        if (pendingSet.add(loc)) {
            pending.push(loc);
        }
    }

    private void pending(int x, int y, int z) {
        ImmutableLocation loc = new ImmutableLocation(x, y, z);
        if (pendingSet.add(loc)) {
            pending.push(loc);
        }
    }

    private static class ImmutableLocation {
        public final int x;
        public final int y;
        public final int z;
        public Block block;

        public ImmutableLocation(int x, int y, int z) {
            this.x = x;
            this.y = y;
            this.z = z;
        }

        public Block getBlock() {
            return this.block;
        }

        public void setBlock(Block block) {
            this.block = block;
        }


        public ImmutableLocation getRelative(BlockDirection direction) {
            return new ImmutableLocation(x + direction.offsetX, y + direction.offsetY, z + direction.offsetZ);
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            } else if (o != null && this.getClass() == o.getClass()) {
                ImmutableLocation that = (ImmutableLocation) o;
                return this.x == that.x && this.y == that.y && this.z == that.z;
            } else {
                return false;
            }
        }

        @Override
        public int hashCode() {
            return Objects.hash(x, y, z);
        }
    }

    private static class LinkedLocation extends ImmutableLocation {
        public int level; // 由方块A遍历到B  B的值会大于A 如果B周围有多余1个方块的值小于B 则有闭环

        public LinkedLocation(int x, int y, int z) {
            super(x, y, z);
        }

        @Override
        public LinkedLocation getRelative(BlockDirection direction) {
            LinkedLocation l = new LinkedLocation(x + direction.offsetX, y + direction.offsetY, z + direction.offsetZ);
            l.level = this.level + 1;
            return l;
        }
    }
}